﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Threading;
using BrainTechLLC;
using BrainTechLLC.ThreadSafeObjects;
using System.Collections.Generic;

namespace ImageAppDataModels
{
    public class ItemAndNumber<T, TUnique> where T : class
    {
        public T Item { get; set; }
        public ThreadSafeStackNonRef<int> Numbers { get; set; }
        public TUnique UniqueKey { get; set; }

        public ItemAndNumber(T item, int number, TUnique uniqueKey)
        {
            Item = item;
            Numbers = new ThreadSafeStackNonRef<int>();
            Numbers.Push(number);
            UniqueKey = uniqueKey;
        }
    }

    public partial class RecentlyUsedList<T, TUnique> : BaseNotifyPropertyChanged, INotifyPropertyChanged where T : class
    {
        protected object _cleanupObject = new object();
        protected FastSmartWeakEvent<EventHandler<ObjectEventArgs<T>>> _itemPushedFromList = new FastSmartWeakEvent<EventHandler<ObjectEventArgs<T>>>();
        public event EventHandler<ObjectEventArgs<T>> ItemPushedFromList { add { _itemPushedFromList.Add(value); } remove { _itemPushedFromList.Remove(value); } }

        public RecentlyUsedList(Func<T, TUnique> getUniqueValue)
        {
            GetUniqueValue = getUniqueValue;
        }

        protected int _tossing;
        public Func<T, TUnique> GetUniqueValue;
        public ThreadSafeLookup<TUnique, ItemAndNumber<T, TUnique>> Lookup = new ThreadSafeLookup<TUnique, ItemAndNumber<T, TUnique>>();

        protected int _maxItems = 10;
        protected int _itemsToTossWhenMaxReached = 3;
        protected ThreadSafeCounter _maxItemNumber = new ThreadSafeCounter();

        public int MaxItemNumber
        {
            get { return _maxItemNumber.Count; }
        }

        public int GetNextMaxItemNumber()
        {
            return _maxItemNumber.Increment();
        }

        public int MaxItems
        {
            get
            {
                return _maxItems;
            }
            set
            {
                if (_maxItems != value)
                {
                    _maxItems = value;
                    OnPropertyChanged("MaxItems");
                }
            }
        }

        public int ItemsToTossWhenMaxReached
        {
            get
            {
                return _itemsToTossWhenMaxReached;
            }
            set
            {
                if (_itemsToTossWhenMaxReached != value)
                {
                    _itemsToTossWhenMaxReached = value;
                    OnPropertyChanged("ItemsToTossWhenMaxReached");
                }
            }
        }

        public int Count
        {
            get { return Lookup.Count; }
        }

        public List<T> Clear()
        {
            List<T> results = new List<T>();
            var all = Lookup.AllItems;

            for (int n = 0; n < all.Count; n++)
            {
                results.Add(all[n].Item);
            }

            Lookup.Clear();
            return results;
        }

        public void RemoveRecentlyUsed(T item)
        {
            TUnique unique = GetUniqueValue(item);
            ItemAndNumber<T, TUnique> entry;

            if (Lookup.TryGetValue(unique, out entry))
            {
                entry.Numbers.PopItem();
            }
        }

        public void MarkItemRecentlyUsed(T item)
        {
            if (item == null)
            {
                Console.Write("item was null");
            }

            TUnique unique = GetUniqueValue(item);
            ItemAndNumber<T, TUnique> entry;

            if (Lookup.TryGetValue(unique, out entry))
            {
                if (entry.Numbers.PeekTop() == MaxItemNumber)
                    return;

                entry.Numbers.Push(GetNextMaxItemNumber());
            }
            else
            {
                entry = new ItemAndNumber<T, TUnique>(item, GetNextMaxItemNumber(), unique);

                Lookup.Add(unique, entry);

                if (_tossing == 0 && Lookup.Count > _maxItems)
                {
                    HandleTossItems();
                }

                OnPropertyChanged("MaxItemNumber");
                OnPropertyChanged("Count");
            }
        }

        public void HandleTossItems()
        {
            if (_tossing == 0 && Lookup.Count > _maxItems)
            {
                var sorted = Lookup.AllItems;
                sorted.Sort(new Comparison<ItemAndNumber<T, TUnique>>((i1, i2) => i1.Numbers.PeekTop().CompareTo(i2.Numbers.PeekTop())));
                Dispatcher dispatcher = Application.Current.Dispatcher;

                lock (_cleanupObject)
                {
                    if (_tossing == 0 && Lookup.Count > _maxItems)
                    {
                        for (int n = 0; n < _itemsToTossWhenMaxReached; n++)
                        {
                            if (n >= sorted.Count)
                                break;

                            var item = sorted[n];

                            if (Lookup.Remove(item.UniqueKey))
                            {
                                _tossing++;
                            }

                            if (dispatcher != null)
                            {
                                dispatcher.BeginInvoke(new OneParamDelegate((o) =>
                                {
                                    _itemPushedFromList.Raise(this, new ObjectEventArgs<T>(o as T));
                                    _tossing--;
                                }),
                                DispatcherPriority.Background, // was normal
                                item.Item);
                            }
                            else
                            {
                                _itemPushedFromList.Raise(this, new ObjectEventArgs<T>(item.Item));
                            }
                        }
                    }
                }
            }
        }
    }
}
